import os
from collections import OrderedDict

from bopress import options


class Hooks(object):
    __actions__ = dict()
    __filters__ = dict()
    __plugins__ = list()
    __admin_menus__ = OrderedDict()
    __files__ = list()

    @staticmethod
    def load(path, callback=None):
        Hooks.__admin_menus__.clear()
        Hooks.__files__.clear()
        Hooks.__filters__.clear()
        Hooks.__actions__.clear()
        Hooks.__plugins__.clear()
        # 在加载用户插件之前执行系统模块
        if callback:
            callback()
        Hooks.__scan__(path)
        for f in Hooks.__files__:
            file_name = os.path.splitext(f)
            c = os.path.relpath(file_name[0], path)
            module_name = ".".join(c.split(os.path.sep))
            p = __import__(module_name)
            name = Hooks.__get_attr__(p, "plugin_name")
            version = Hooks.__get_attr__(p, "version")
            if name and version:
                uri = Hooks.__get_attr__(p, "plugin_uri")
                desc = Hooks.__get_attr__(p, "description")
                author = Hooks.__get_attr__(p, "author")
                author_uri = Hooks.__get_attr__(p, "author_uri")
                plugin = dict()
                plugin["path"] = f
                plugin["module_name"] = module_name
                plugin["uri"] = uri
                plugin["description"] = desc
                plugin["author"] = author
                plugin["author_uri"] = author_uri
                plugin["version"] = version
                Hooks.__plugins__.append(plugin)
        for ak in Hooks.__actions__:
            v = Hooks.__actions__[ak]
            Hooks.__actions__[ak] = sorted(v, key=lambda s: s['priority'])

        for ak in Hooks.__filters__:
            v = Hooks.__filters__[ak]
            Hooks.__filters__[ak] = sorted(v, key=lambda s: s['priority'])

    @staticmethod
    def __get_attr__(o, name, default=""):
        try:
            return getattr(o, name)
        except AttributeError as e:
            return default

    @staticmethod
    def __scan__(path):
        files = os.scandir(path)
        for f in files:
            if f.is_dir():
                Hooks.__scan__(f.path)
            else:
                if f.name == "meta.py":
                    Hooks.__files__.append(f.path)


class AdminMenus(object):
    @staticmethod
    def get_menu(menu_slug):
        return Hooks.__admin_menus__.get(menu_slug, None)

    @staticmethod
    def do_admin_menus(handler, current_menu_item):
        menus_ = OrderedDict()
        for item in sorted(Hooks.__admin_menus__.values(), key=lambda s: s["position"]):
            if not item["parent"]:
                menus_[item["menu_slug"]] = item
        htmls = list()
        AdminMenus.list_menus(menus_, htmls, handler, current_menu_item)
        return "".join(htmls)

    @staticmethod
    def list_menus(menus, htmls=list(), handler=None, current_menu_item=None):
        for k in menus.keys():
            menu_item = menus.get(k)
            if not menu_item:
                continue
            if menu_item["place"] != 'menu':
                continue
            caps = menu_item["capability"]
            if not handler.auth(set(caps), False, False):
                continue
            href = "%s?page=%s" % (handler.reverse_url("admin"), menu_item["menu_slug"])
            has_children = menu_item["has_children"]
            if has_children:
                css_cls = ""
                if k in current_menu_item["path"]:
                    css_cls = "active"
                htmls.append('<li class="treeview %s">' % css_cls)
                htmls.append("""
                                    <a href="%s">
                                        <i class="fa fa-square-o"></i> <span>%s</span>
                                        <span class="pull-right-container">
                                          <i class="fa fa-angle-left pull-right"></i>
                                        </span>
                                      </a>
                                """ % ("#", menu_item["menu_title"]))
                menus_ = OrderedDict()
                for item in Hooks.__admin_menus__.values():
                    if item["parent"] == menu_item["menu_slug"]:
                        menus_[item["menu_slug"]] = item
                if not css_cls:
                    htmls.append('<ul class="treeview-menu">')
                else:
                    htmls.append('<ul class="treeview-menu menu-open">')
                AdminMenus.list_sub_menus(menus_, htmls, handler, current_menu_item)
                htmls.append('</ul>')
                htmls.append('</li>')
            else:
                css_cls = ""
                if k == current_menu_item["menu_slug"]:
                    css_cls = "active"
                htmls.append("""
                    <li class="%s"><a href="%s"><i class="fa fa-circle-o"></i> <span>%s<span></a></li>
                """ % (css_cls, href, menu_item["menu_title"]))

    @staticmethod
    def list_sub_menus(menus, htmls=list(), handler=None, current_menu_item=None):
        items = sorted(menus.values(), key=lambda s: s["position"])
        for menu_item in items:
            if not menu_item:
                continue
            if menu_item["place"] != 'menu':
                continue
            caps = menu_item["capability"]
            if not handler.auth(set(caps), False, False):
                continue
            k = menu_item["menu_slug"]
            href = "%s?page=%s" % (handler.reverse_url("admin"), menu_item["menu_slug"])
            has_children = menu_item["has_children"]
            if has_children:
                css_cls = ""
                if k in current_menu_item["path"]:
                    css_cls = "active"
                htmls.append('<li class="%s">' % css_cls)
                htmls.append("""
                        <a href="%s">
                            <i class="fa fa-square-o"></i> <span>%s</span>
                            <span class="pull-right-container">
                              <i class="fa fa-angle-left pull-right"></i>
                            </span>
                          </a>
                    """ % ("#", menu_item["menu_title"]))
                menus_ = OrderedDict()
                for item in Hooks.__admin_menus__.values():
                    if item["parent"] == menu_item["menu_slug"]:
                        menus_[item["menu_slug"]] = item
                if not css_cls:
                    htmls.append('<ul class="treeview-menu">')
                else:
                    htmls.append('<ul class="treeview-menu menu-open">')
                AdminMenus.list_sub_menus(menus_, htmls, handler, current_menu_item)
                htmls.append('</ul>')
                htmls.append('</li>')
            else:
                css_cls = ""
                if k == current_menu_item["menu_slug"]:
                    css_cls = "active"
                htmls.append("""
                        <li class="%s"><a href="%s"><i class="fa fa-circle-o"></i> <span>%s<span></a></li>
                    """ % (css_cls, href, menu_item["menu_title"]))


def add_action(name="", func=None, priority=10):
    """
    在某一个扩展点执行一些动作
    :param name: 扩展点名称
    :param func: 自定义函数
    :param priority:
    :return:
    """
    if not name:
        return
    action = dict()
    action["func"] = func
    action["priority"] = priority
    if name.startswith("bo_api_"):
        b = name.split("_")
        if len(b) > 3:
            action["api_action"] = "_".join(b[3:])
            name = "_".join(b[0:3])
        else:
            return
    if name in Hooks.__actions__:
        Hooks.__actions__[name].append(action)
    else:
        Hooks.__actions__[name] = [action]


def do_action(name="", *args, **kwargs):
    """
    安置及执行扩展点，供其它模块扩展
    :param name: 扩展点名称
    :param args:
    :param kwargs:
    :return:
    """
    if not name:
        return None
    if name in Hooks.__actions__:
        arr = Hooks.__actions__[name]
        for item in arr:
            if "api_action" in item:
                api_action = item["api_action"]
                if args[1] == api_action:
                    item["func"](args[0])
            else:
                item["func"](*args, **kwargs)


def add_filter(name="", func=None, priority=10):
    """
    过滤器
    在某一扩展点对一些值或内容进行修改
    :param name:扩展点名称
    :param func: 自定义函数，函数的第一个参数默认为修改的内容
    :param priority:
    :return:
    """
    if not name:
        return
    action = dict()
    action["func"] = func
    action["priority"] = priority
    if name in Hooks.__filters__:
        Hooks.__filters__[name].append(action)
    else:
        Hooks.__filters__[name] = [action]


def apply_filters(name="", v=None, *args, **kwargs):
    """
    安置及执行过滤器
    :param name:扩展点名称
    :param v: 可以过滤的值
    :param args:
    :param kwargs:
    :return:
    """
    if name in Hooks.__filters__:
        arr = Hooks.__filters__[name]
        for item in arr:
            v = item["func"](v, *args, **kwargs)
    return v


def add_menu_page(page_title, menu_title, menu_slug='',
                  capability=list(), cb=None, icon_url='', position=10, place="menu"):
    """
    添加菜单
    :param page_title: 页标题
    :param menu_title: 菜单标题
    :param menu_slug: 菜单ID
    :param capability: 访问权限
    :param cb: str or func
    :param icon_url: 菜单图标
    :param position: 菜单位置
    :param place: default is `menu`, other is `single`, `quick`
    """
    menu = OrderedDict()
    menu["page_title"] = page_title
    menu["menu_title"] = menu_title
    menu["capability"] = capability
    menu["menu_slug"] = menu_slug
    menu["cb"] = cb
    menu["parent"] = ""
    menu["path"] = list()
    menu["icon_url"] = icon_url
    menu["position"] = position
    menu["has_children"] = False
    menu["place"] = place
    Hooks.__admin_menus__[menu_slug] = menu


def add_submenu_page(parent_slug, page_title, menu_title, menu_slug='', capability=list(),
                     cb=None, icon_url='', position=10, place="menu"):
    """
    添加子菜单
    :param parent_slug: 父菜单ID
    :param page_title: 页标题
    :param menu_title: 菜单标题
    :param menu_slug: 菜单ID
    :param capability: 访问权限
    :param cb: str or func
    :param icon_url: 菜单图标
    :param position: 菜单图标
    :param place:  default is `menu`, other is `single`, `quick`
    :return:
    """
    parent = Hooks.__admin_menus__.get(parent_slug)
    if not parent:
        return
    menu = OrderedDict()
    menu["page_title"] = page_title
    menu["menu_title"] = menu_title
    menu["capability"] = capability
    menu["menu_slug"] = menu_slug
    menu["cb"] = cb
    menu["icon_url"] = icon_url
    menu["position"] = position
    menu["parent"] = parent_slug
    menu["has_children"] = False
    menu["path"] = parent["path"] + [parent_slug]
    parent["has_children"] = True
    menu["place"] = place
    Hooks.__admin_menus__[menu_slug] = menu


def add_option_page(page_title, menu_title, menu_slug, capability=None, icon_url=""):
    """
    添加选项页菜单
    :param page_title: 页标题
    :param menu_title: 菜单标题
    :param menu_slug: 菜单ID
    :param capability: 访问权限
    :param icon_url: icon
    """
    if not capability:
        capability = ["manager_option"]
    add_submenu_page("bo-options-general", page_title, menu_title, menu_slug,
                     capability, "admin/options-general.html", icon_url)


def add_code_generation_menu_page(page_title, menu_title, menu_slug='', position=10):
    """
    添加代码生成菜单页面
    :param page_title:
    :param menu_title:
    :param menu_slug:
    :param position:
    """
    add_menu_page(page_title, menu_title, menu_slug, ["list_users"], "admin/code-generation.html", "", position)


def add_code_generation_submenu_page(parent_slug, page_title, menu_title, menu_slug=''):
    """
    添加代码生成子菜单页面
    :param parent_slug:
    :param page_title:
    :param menu_title:
    :param menu_slug:
    """
    add_submenu_page(parent_slug, page_title, menu_title, menu_slug, ["list_users"], "admin/code-generation.html", "")


def add_capability(name, group="general", description=""):
    """
    添加权限点
    :param name: 名称 唯一
    :param group: 分组 便于权限分配
    :param description: 此权限的用处说明
    :return:
    """
    c = options.get_options("bo_capabilities")
    if not c:
        return
    c[name] = (group, description)


def add_role(name, display_name="", capabilities=None):
    """
    添加角色
    :param name: 角色名称
    :param display_name: 角色显示名称
    :param capabilities: 权限点, list type
    :return:
    """
    if type(capabilities) is not list:
        print("capabilities require list type")
        return
    c = options.get_options("bo_roles")
    if not c:
        return
    if not display_name:
        display_name = name
    if not capabilities:
        capabilities = set()
    c[name] = [display_name, capabilities]
